package me.lubinn.Incantatio.Spells;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import me.lubinn.Incantatio.Incantatio;
import net.minecraft.server.EntityFireball;
import net.minecraft.server.EntityLiving;
import net.minecraft.server.EntityTNTPrimed;

import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.TreeType;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.util.Vector;

public abstract class Spell
{
	//targeting parameters
	protected boolean targeted = true;
	protected double rangeBase = 10;
	protected double rangeGrowth = 5;
	
	static final Map<String, Spell> spells = new HashMap<String, Spell>();
	static
	{
		spells.put("salio", new Salio());
		spells.put("praemium", new Praemium());
		spells.put("fulmen", new Fulmen());
		spells.put("domus", new Domus());
		spells.put("pluvia", new Pluvia());
		spells.put("tempestas", new Tempestas());
		spells.put("sereno", new Sereno());
		spells.put("lucem", new Lucem());
		spells.put("noctem", new Noctem());
		spells.put("arbore", new Arbore());
		spells.put("chasma tempestas", new ChasmaTempestas());
		spells.put("foobar", new RealityCheck());
		spells.put("vorto", new Vorto());
	}
	
	protected int Range(int size) //gets the distance away to target this spell when augmented with flight
	{
		return (int)(rangeBase + size*rangeGrowth);
	}
	protected Vector MoveVector(int size) //where to move when spell augmented with motion
	{
		return new Vector(0, 1, 0);
	}
	public abstract Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments); //casts the spell, returns a reference to a copy for tracking

	public static Spell GetSpell(String spellName)
	{
		if (spells.containsKey(spellName))
			return spells.get(spellName);
		if (ConstructionSpell.spells.containsKey(spellName))
			return ConstructionSpell.spells.get(spellName); 
		Incantatio.log.info("Spell "+spellName+" not found!");
		return null;
	}
	
}

class Salio extends Spell
{
    public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
    {
        List<Block> blocks = event.getPlayer().getLastTwoTargetBlocks(null, 500);
        World world = event.getPlayer().getWorld();
        boolean motion = augments.containsKey("motion"); 
        Location location = ((Block)blocks.get(motion ? 1 : 0)).getLocation();
        world.playEffect(location, Effect.SMOKE, 1);
        world.playEffect(event.getPlayer().getLocation(), Effect.SMOKE, 1);
        location.add(0.5, 0, 0.5);
        if (motion)
        	location.add(0, 1, 0);
        event.getPlayer().teleport(location);
        return this;
    }
}
class Praemium extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Player player = event.getPlayer();
	    World wld = player.getWorld();
	    int w = 1;
	    if(!Incantatio.config.getBoolean("praemium_replaces_block", false))
	    {
	        w = 0;
	    }
	    List<Block> blocks = player.getLastTwoTargetBlocks(null, 500);
	    Block block = (Block)blocks.get(w);
	    blocks.clear();
	    blocks.add(block);
	    for (int i = 0; i < (int)(0.5*size*(size+1)) + 1; i++)
	    {
	    	Vector r = Vector.getRandom().subtract(new Vector(0.5, 0.5, 0.5)).normalize().multiply(3+size);
	    	Vector v = block.getLocation().toVector().add(r);
	    	Block b = wld.getBlockAt(v.toLocation(wld));
	    	blocks.add(b);
	    }
	    
	    float expSize = 4f + 0.5f*size;
	    boolean force = augments.containsKey("force");
	    if (force)
	    	expSize *= 1.5;
	    for (int i = 0; i < blocks.size(); i++)
	    {
	    	block = blocks.get(i);
            CraftWorld world = (CraftWorld)block.getWorld();
	        if (augments.containsKey("book"))
	        {
	            event.getPlayer().getWorld().createExplosion(block.getLocation(), expSize, force);
	            world.playEffect(block.getLocation(), Effect.SMOKE, 1);
	        } 
	        else
	        {
	            EntityTNTPrimed tnt = new EntityTNTPrimed(world.getHandle(), (float)block.getX() + 0.5F, (float)block.getY() + 0.5F, (float)block.getZ() + 0.5F);
	            tnt.yield = expSize;
	            world.getHandle().addEntity(tnt);
	            block.getWorld().playEffect(block.getLocation(), Effect.SMOKE, 1);
	        }
	    }
		return this;
	}
}
class Fulmen extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		boolean force = augments.containsKey("force");
		boolean book = augments.containsKey("book");
	    Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
	    int xorg = location.getBlockX();
	    int yorg = location.getBlockY();
	    int zorg = location.getBlockZ();
	    World world = event.getPlayer().getWorld();
	    ArrayList<Location> strikelocs = new ArrayList<Location>(60);
	    strikelocs.add(location);
	    int radius = 1 + (int)(0.45*size);
	    int nBolts = 1 + (int)(0.2*size*size + 0.015*size*size*size);
	    if(book)
	    {
	    	radius *= 5;
	    	nBolts = 5 + 5*nBolts + (int)(0.12*nBolts*nBolts);
	    }
	    for(int x = -radius; x <= radius; x++)
	    {
	        for(int y = -radius; y <= radius; y++)
	        {
	            for(int z = -radius; z <= radius; z++)
	            {
	            	if (Math.sqrt(x*x + y*y + z*z) <= radius)
	            	{
	                    Block block = world.getBlockAt(x+xorg, y+yorg, z+zorg);
	                    Location tloc = block.getLocation();
	                    if(block.getType() != Material.AIR && (block.getRelative(BlockFace.NORTH).getType() == Material.AIR ||
	                    			block.getRelative(BlockFace.SOUTH).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.EAST).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.WEST).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.UP).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.DOWN).getType() == Material.AIR))
	                        strikelocs.add(tloc);
	            	}
	            }
	        }
	    }
	    for(int m = 0; m < nBolts; m++)
	    {
	        int choice = (int)(Math.random() * (double)strikelocs.size());
	        Location loc = strikelocs.get(choice);
	        world.strikeLightning(loc);
	        if (force)
	        {
	        	world.getBlockAt(loc).getRelative(0, 1, 0).setType(Material.FIRE);
	        	if (book)
	        		world.createExplosion(loc, 3f);
	        }
	        if (nBolts-m > strikelocs.size())
	        	strikelocs.remove(choice);
	    }
	    return this;
	}
}
class Domus extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    player.getWorld().playEffect(player.getLocation(), Effect.SMOKE, 1);
	    player.teleport(player.getWorld().getSpawnLocation());
	    return this;
	}
}
class Sereno extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(false);
	    event.getPlayer().getWorld().setThundering(false);
	    return this;
	}
}
class Tempestas extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(true);
	    event.getPlayer().getWorld().setThundering(true);
	    return this;
	}
}
class Pluvia extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(true);
	    event.getPlayer().getWorld().setThundering(false);
	    return this;
	}
}
class Lucem extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		event.getPlayer().getWorld().setTime(23425L);
	    return this;
	}
}
class Noctem extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		event.getPlayer().getWorld().setTime(12550L);
	    return this;
	}
}

class Arbore extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    World world = event.getPlayer().getWorld();
	    Block block = event.getPlayer().getTargetBlock(null, 500);
	    //block.setType(Material.DIRT);
	    world.generateTree(block.getLocation().add(0, 1, 0), TreeType.TREE);
	    return this;
	}
}
class ChasmaTempestas extends Spell
{
	public Spell CastSpell(final PlayerChatEvent event, Material mat, final int size, Map<String, Boolean> augments)
	{
        //OK, 5 seconds duration of this, 1 second between storm 
        
        long delay = 0; //milliseconds
        long period = 1000; //milliseconds
        Timer meteorTimer = new Timer();
        
        //Timing thinger
        TimerTask task = new TimerTask()
        {
                int iterations = 0;
            public void run() 
            {
                CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
        
                CraftPlayer craftPlayer = (CraftPlayer) event.getPlayer();
                EntityLiving playerEntity = craftPlayer.getHandle();            
                
                for (int i = 0; i < (int)(2.0*size*size) + 1; i++)
                {
                        //FIXME: KEEPS UPDATING BLOCK, can channel it and look around and it follows you, i want it to stay at that orig. spot
                        Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
                        Vector aim = event.getPlayer().getEyeLocation().getDirection().normalize();
                                
                        location.setX(location.getX());
                        location.setY(120);
                        location.setZ(location.getZ());
                        
                        Vector r = Vector.getRandom().subtract(new Vector(0.5, 0.5, 0.5)).normalize().multiply(2+size);
                        Vector v = location.toVector().add(r);
                
                
                        EntityFireball fireball = new EntityFireball(((CraftWorld) craftPlayer.getWorld()).getHandle(), playerEntity,0,-1,0);
                
                        //fireball.setDirection(aim.getX(), aim.getY(), aim.getZ());
                
                        fireball.locX = v.getX();
                        fireball.locY = v.getY();
                        fireball.locZ = v.getZ();
                
//                              fireball.motX = aim.getX() * .1;
//                              fireball.motY = aim.getY() * .1;
//                              fireball.motZ = aim.getZ() * .1;
                
                        ((CraftWorld) craftPlayer.getWorld()).getHandle().addEntity(fireball);
                                
                }
             //HOW DO I STOP THIS CRAZY THING
	             iterations++;
	             if (iterations >= 6)
	                     this.cancel();
            }
        };
        
        //do it once on cast, then each subsequent 1 seconds for 6 seconds
        meteorTimer.scheduleAtFixedRate(task, delay, period);           
	    return this;
	}
}
class RealityCheck extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Material [][][] temp = new Material [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
		Block [][][] block = new Block [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		
	    for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					temp[i][j][k] = location.add(i, j, k).getBlock().getType();
					block[i][j][k] = location.add(i,j,k).getBlock();
				}
			}
		}
		
    	for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					block[j][i][k].setType(Material.STONE);  //setType(temp[i][j][k]);
				}
			}
		}
    	return this;
	}
}

class Vorto extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Material [][][] temp = new Material [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		Location location2 = location;
		
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
		Block [][][] block = new Block [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		
	    for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					temp[i][j][k] = location2.add(i, j, k).getBlock().getType();
					location2=location.clone();
					block[i][j][k] = location2.add(i,j,k).getBlock();
					location2=location.clone();
				}
			}
		}
		
    	for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					block[j][i][k].setType(temp[i][j][k]);
				}
			}
		}
    	return this;
	}
}








/*
 *		Adding spells
 *		class NewSpell extends Spell
 *
 *  	in IncantatioSpell, keep Map<String, Spell> spellTypes
 *  	when casting a spell, spellTypes.get("spellName").CastSpell(params);
 *  	if needing an over-time reference, spellTypes.get("spellName").InstantiateSpell(params);
 */
